﻿
using System;
using System.Collections.Generic;
using Xunit;
using Xunit.Extensions;

namespace EmperialApps.WeatherSpark.Data {

    public class TestConvertValue {

        [Theory]
        [InlineData( 0.0 )]
        [InlineData( 1.0 )]
        public void Identity_ReturnsGivenValue( double value ) {
            double actual = ConvertValue.Identity( value );

            Assert.Equal( value, actual );
        }


        private const string Temperatures_Name = "Temperatures";
        public static IEnumerable<object[]> Temperatures {
            get {
                yield return new object[] { 0.0, 32.0 };
                yield return new object[] { 10.0, 50.0 };
                yield return new object[] { 16.0, 60.8 };
                yield return new object[] { 22.2, 72.0 };
                yield return new object[] { 37.0, 98.6 };
                yield return new object[] { -40.0, -40.0 };
                yield return new object[] { 100.0, 212.0 };
            }
        }

        [Theory]
        [PropertyData( Temperatures_Name )]
        public void ToCelsius_ReturnsCelsiusResult( double celsius, double fahrenheit ) {
            double actual = ConvertValue.ToCelsius( fahrenheit );

            Assert.Equal( celsius, actual );
        }

        [Theory]
        [PropertyData( Temperatures_Name )]
        public void ToFahrenheit_ReturnsFahrenheitResult( double celsius, double fahrenheit ) {
            double actual = ConvertValue.ToFahrenheit( celsius );

            Assert.Equal( fahrenheit, actual );
        }


        private const string Distances_Name = "Distances";
        public static IEnumerable<object[]> Distances {
            get {
                yield return new object[] { 0.0, 0.0 };
                yield return new object[] { 1.6, 1.0 };
                yield return new object[] { 16.1, 10.0 };
                yield return new object[] { 60.0, 37.3 };
                yield return new object[] { 99.9, 62.1 };
            }
        }

        [Theory]
        [PropertyData( Distances_Name )]
        public void ToKilometers_ReturnsKilometersResult( double kilometers, double miles ) {
            double actual = ConvertValue.ToKilometers( miles );

            Assert.Equal( kilometers, actual );
        }

        [Theory]
        [PropertyData( Distances_Name )]
        public void ToMiles_ReturnsMilesResult( double kilometers, double miles ) {
            double actual = ConvertValue.ToMiles( kilometers );

            Assert.Equal( miles, actual );
        }


        private const string Pressures_Name = "Pressures";
        public static IEnumerable<object[]> Pressures {
            get {
                yield return new object[] { 0.0, 0.0 };
                yield return new object[] { 799.2, 23.6 };
                yield return new object[] { 1012.5, 29.9 };
                yield return new object[] { 1087.0, 32.1 };
            }
        }

        [Theory]
        [PropertyData( Pressures_Name )]
        public void ToHectopascal_ReturnsHectopascalResult( double hectopascals, double inchesOfMercury ) {
            double actual = ConvertValue.ToHectopascals( inchesOfMercury );

            Assert.Equal( hectopascals, actual );
        }

        [Theory]
        [PropertyData( Pressures_Name )]
        public void ToInchesOfMercury_ReturnsInchesOfMercuryResult( double hectopascals, double inchesOfMercury ) {
            double actual = ConvertValue.ToInchesOfMercury( hectopascals );

            Assert.Equal( inchesOfMercury, actual );
        }


        private const string Speeds_Name = "Speeds";
        public static IEnumerable<object[]> Speeds {
            get {
                yield return new object[] { 0.0, 0.0 };
                yield return new object[] { 4.5, 10.1 };
                yield return new object[] { 10.0, 22.4 };
                yield return new object[] { 26.8, 60.0 };
            }
        }

        [Theory]
        [PropertyData( Speeds_Name )]
        public void ToMetersPerSecond_ReturnsMetersPerSecondResult( double metersPerSecond, double milesPerHour ) {
            double actual = ConvertValue.ToMetersPerSecond( milesPerHour );

            Assert.Equal( metersPerSecond, actual );
        }

        [Theory]
        [PropertyData( Speeds_Name )]
        public void ToMilesPerHour_ReturnsMilesPerHourResult( double metersPerSecond, double milesPerHour ) {
            double actual = ConvertValue.ToMilesPerHour( metersPerSecond );

            Assert.Equal( milesPerHour, actual );
        }


        private const string Directions_Name = "Directions";
        public static IEnumerable<object[]> Directions {
            get {
                yield return new object[] { 0.0, CompassDirection.N };
                yield return new object[] { 10.0, CompassDirection.N };
                yield return new object[] { 20.0, CompassDirection.NNE };
                yield return new object[] { 30.0, CompassDirection.NNE };
                yield return new object[] { 40.0, CompassDirection.NE };
                yield return new object[] { 50.0, CompassDirection.NE };
                yield return new object[] { 60.0, CompassDirection.ENE };
                yield return new object[] { 70.0, CompassDirection.ENE };
                yield return new object[] { 80.0, CompassDirection.E };
                yield return new object[] { 90.0, CompassDirection.E };
                yield return new object[] { 100.0, CompassDirection.E };
                yield return new object[] { 110.0, CompassDirection.ESE };
                yield return new object[] { 120.0, CompassDirection.ESE };
                yield return new object[] { 130.0, CompassDirection.SE };
                yield return new object[] { 140.0, CompassDirection.SE };
                yield return new object[] { 150.0, CompassDirection.SSE };
                yield return new object[] { 160.0, CompassDirection.SSE };
                yield return new object[] { 170.0, CompassDirection.S };
                yield return new object[] { 180.0, CompassDirection.S };
                yield return new object[] { 190.0, CompassDirection.S };
                yield return new object[] { 200.0, CompassDirection.SSW };
                yield return new object[] { 210.0, CompassDirection.SSW };
                yield return new object[] { 220.0, CompassDirection.SW };
                yield return new object[] { 230.0, CompassDirection.SW };
                yield return new object[] { 240.0, CompassDirection.WSW };
                yield return new object[] { 250.0, CompassDirection.WSW };
                yield return new object[] { 260.0, CompassDirection.W };
                yield return new object[] { 270.0, CompassDirection.W };
                yield return new object[] { 280.0, CompassDirection.W };
                yield return new object[] { 290.0, CompassDirection.WNW };
                yield return new object[] { 300.0, CompassDirection.WNW };
                yield return new object[] { 310.0, CompassDirection.NW };
                yield return new object[] { 320.0, CompassDirection.NW };
                yield return new object[] { 330.0, CompassDirection.NNW };
                yield return new object[] { 340.0, CompassDirection.NNW };
                yield return new object[] { 350.0, CompassDirection.N };
            }
        }

        [Theory]
        [PropertyData( Directions_Name )]
        public void ToCompassDirection_ReturnsCompassResult( double degrees, CompassDirection direction ) {
            CompassDirection actual = ConvertValue.ToCompassDirection( degrees );

            Assert.Equal( direction, actual );
        }


        private const string Modes_Name = "Modes";
        public static IEnumerable<object[]> Modes {
            get {
                yield return new object[] { ForecastTileMode.MetricDay, Units.Metric, ForecastDisplayMode.Day, false, false };
                yield return new object[] { ForecastTileMode.MetricHour, Units.Metric, ForecastDisplayMode.Hour, false, false };
                yield return new object[] { ForecastTileMode.ImperialDay, Units.Imperial, ForecastDisplayMode.Day, false, false };
                yield return new object[] { ForecastTileMode.ImperialHour, Units.Imperial, ForecastDisplayMode.Hour, false, false };
                yield return new object[] { ForecastTileMode.WideMetricDay, Units.Metric, ForecastDisplayMode.Day, false, true };
                yield return new object[] { ForecastTileMode.WideMetricHour, Units.Metric, ForecastDisplayMode.Hour, false, true };
                yield return new object[] { ForecastTileMode.WideImperialDay, Units.Imperial, ForecastDisplayMode.Day, false, true };
                yield return new object[] { ForecastTileMode.WideImperialHour, Units.Imperial, ForecastDisplayMode.Hour, false, true };
                yield return new object[] { ForecastTileMode.BackedMetricDay, Units.Metric, ForecastDisplayMode.Day, true, false };
                yield return new object[] { ForecastTileMode.BackedMetricHour, Units.Metric, ForecastDisplayMode.Hour, true, false };
                yield return new object[] { ForecastTileMode.BackedImperialDay, Units.Imperial, ForecastDisplayMode.Day, true, false };
                yield return new object[] { ForecastTileMode.BackedImperialHour, Units.Imperial, ForecastDisplayMode.Hour, true, false };
                yield return new object[] { ForecastTileMode.WideBackedMetricDay, Units.Metric, ForecastDisplayMode.Day, true, true };
                yield return new object[] { ForecastTileMode.WideBackedMetricHour, Units.Metric, ForecastDisplayMode.Hour, true, true };
                yield return new object[] { ForecastTileMode.WideBackedImperialDay, Units.Imperial, ForecastDisplayMode.Day, true, true };
                yield return new object[] { ForecastTileMode.WideBackedImperialHour, Units.Imperial, ForecastDisplayMode.Hour, true, true };
            }
        }

        [Theory]
        [PropertyData( Modes_Name )]
        [InlineData( ForecastTileMode.MetricDay, Units.Metric, ForecastDisplayMode.None, false, false )]
        public void ToTileMode_ReturnsTileModeResult( ForecastTileMode tileMode, Units units, ForecastDisplayMode displayMode, bool displayBack, bool displayWide ) {
            ForecastTileMode actual = ConvertValue.ToTileMode( units, displayMode, displayBack, displayWide );

            Assert.Equal( tileMode, actual );
        }

        [Theory]
        [PropertyData( Modes_Name )]
        public void ToTileMode_ReturnsDisplayValueResult( ForecastTileMode tileMode, Units units, ForecastDisplayMode displayMode, bool displayBack, bool displayWide ) {
            var expected = Tuple.Create( units, displayMode, displayBack, displayWide );

            Units actualUnits;
            ForecastDisplayMode actualDisplayMode;
            bool actualDisplayBack, actualDisplayWide;
            ConvertValue.FromTileMode( tileMode, out actualUnits, out actualDisplayMode, out actualDisplayBack, out actualDisplayWide );
            var actual = Tuple.Create( actualUnits, actualDisplayMode, actualDisplayBack, actualDisplayWide );

            Assert.Equal( expected, actual );
        }


        private const string Symbols_Name = "Symbols";
        public static IEnumerable<object[]> Symbols {
            get {
                foreach( YrnoSymbol symbol in Enum.GetValues( typeof( YrnoSymbol ) ) )
                    yield return new object[] { symbol };
            }
        }

        private static double GetExpectedPrecipitationPotential( YrnoSymbol symbol ) {
            int expected;

            string name = symbol.ToString( );
            if( name.Contains( "Clear" )
             || name.Contains( "Fair" ) ) {
                expected = 00;
            }
            else if( name.Contains( "Cloudy" ) ) {
                expected = 20;
                if( name.Contains( "Partly" ) )
                    expected -= 10;
            }
            else if( name.Contains( "Fog" ) ) {
                expected = 30;
            }
            else {
                expected = 70;
                if( name.Contains( "Thunder" ) )
                    expected += 10;
                if( name.Contains( "Showers" ) )
                    expected -= 20;
                if( name.Contains( "Heavy" ) )
                    expected += 20;
            }

            return expected / 100.0;
        }

        private static double GetExpectedSkyCover( YrnoSymbol symbol ) {
            int expected;

            string name = symbol.ToString( );
            if( name.Contains( "Clear" ) ) {
                expected = 00;
            }
            else if( name.Contains( "Fair" ) ) {
                expected = 20;
            }
            else if( name.Contains( "Cloudy" ) ) {
                expected = 80;
                if( name.Contains( "Partly" ) )
                    expected -= 30;
            }
            else if( name.Contains( "Fog" ) ) {
                expected = 40;
            }
            else if( name.Contains( "Showers" ) ) {
                expected = 90;
            }
            else {
                expected = 100;
            }

            return expected / 100.0;
        }

        [Theory]
        [PropertyData( Symbols_Name )]
        public void ToPrecipitationPotential_ReturnsDecimalResult( YrnoSymbol symbol ) {
            double expected = GetExpectedPrecipitationPotential( symbol );

            double actual = ConvertValue.ToPrecipitationPotential( symbol );

            Assert.Equal( expected, actual );
        }

        [Theory]
        [PropertyData( Symbols_Name )]
        public void ToSkyCover_ReturnsDecimalResult( YrnoSymbol symbol ) {
            double expected = GetExpectedSkyCover( symbol );

            double actual = ConvertValue.ToSkyCover( symbol );

            Assert.Equal( expected, actual );
        }


        private const string Angles_Name = "Angles";
        public static IEnumerable<object[]> Angles {
            get {
                yield return new object[] { 0.0, 0.0 };
                yield return new object[] { 30.0, Math.PI / 6 };
                yield return new object[] { 45.0, Math.PI / 4 };
                yield return new object[] { 60.0, Math.PI / 3 };
                yield return new object[] { 90.0, Math.PI / 2 };
                yield return new object[] { 180.0, Math.PI };
                yield return new object[] { 270.0, Math.PI * 3 / 2 };
                yield return new object[] { 360.0, Math.PI * 2 };
            }
        }

        [Theory]
        [InlineData( 0.0, 0.0 )]
        [InlineData( 33.3333, 30.0 )]
        [InlineData( -90.0, 270.0 )]
        [InlineData( 175.0, 180.0 )]
        [InlineData( 360.0, 0.0 )]
        [InlineData( 400.0, 0.0 )]
        [InlineData( -400.0, 0.0 )]
        public void ToDirection_ReturnsInRangeValue( double value, double expected ) {
            double actual = ConvertValue.ToDirection( value );

            Assert.Equal( expected, actual );
        }

        [Theory]
        [PropertyData( Angles_Name )]
        public void ToDegrees_ReturnsDegreeResult( double degrees, double radians ) {
            double actual = ConvertValue.ToDegrees( radians );

            Assert.Equal( degrees, actual, 9 );
        }

        [Theory]
        [PropertyData( Angles_Name )]
        public void ToRadians_ReturnsRadianResult( double degrees, double radians ) {
            double actual = ConvertValue.ToRadians( degrees );

            Assert.Equal( radians, actual, 9 );
        }


        private const string Percentages_Name = "Percentages";
        public static IEnumerable<object[]> Percentages {
            get {
                yield return new object[] { 0, 0.0 };
                yield return new object[] { 6, 0.06 };
                yield return new object[] { 20, 0.2 };
                yield return new object[] { 24, 0.24 };
                yield return new object[] { 24.6, 0.25 };
                yield return new object[] { 100, 1.0 };
            }
        }

        [Theory]
        [PropertyData( Percentages_Name )]
        [InlineData( int.MinValue, double.NaN )]
        public void ToDecimal_ReturnsDecimalResult( double percentage, double decimalValue ) {
            double actual = ConvertValue.ToDecimal( percentage );

            Assert.Equal( decimalValue, actual );
        }

        [Theory]
        [PropertyData( Percentages_Name )]
        public void ToPercentage_ReturnsPercentageResult( double percentage, double decimalValue ) {
            double expected = Math.Round( percentage );

            double actual = ConvertValue.ToPercentage( decimalValue );

            Assert.Equal( expected, actual );
        }

    }

}
